#define _FILE_OFFSET_BITS 64

#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cerrno>
#include <sys/stat.h>

#include "tools.h"

// --------------
void print_help()
// --------------
{
	printf("THIS SOFTWARE COMES WITH NO WARRANTY. USE AT YOUR OWN RISK.\n");
	printf("Usage: mkfs.stagfs <path> [-n <node-size>] [-s <total-size>]\n");
	printf("Creates a SimpleTagFS filesystem.\n\n");
	
	printf("   <path>           path to a file or a block device\n");
	printf("   <node-size>      (integer up to 2^16-1) node size in bytes.\n"); // 80
	printf("                    Defaults to device's st_blksize or 4096.\n");
	printf("                    Recommended values are multiples of 4096.\n");
	printf("   <total-size>     (integer up to 2^64-1) total filesystem size in bytes.\n");
	printf("                    Ignored on device nodes.\n");
	printf("                    Header is 6 bytes long.\n");
	printf("More information available at http://asth.net/(+blog).\n");
}

// -----------------------------
int main(int argc, char *argv[])
// -----------------------------
{
	uint64_t size = 0;
	uint16_t nodesize = 0;

	if (argc > 2)
	{
		if (argc % 2 != 0)
		{
			print_help();
			return EXIT_FAILURE;
		}
		enum {ARG_N, ARG_S, NO_ARG} arg = NO_ARG;
		for (uint8_t i=2; i<argc; ++i)
		{
			if (strcmp(argv[i], "-n") == 0)
			{
				arg = ARG_N;
			}
			else if (strcmp(argv[i], "-s") == 0)
			{
				arg = ARG_S;
			}
			else if (arg == ARG_N && argv[i][0] != '-')
			{
				nodesize = atoi(argv[i]);
				arg = NO_ARG;
			}
			else if (arg == ARG_S && argv[i][0] != '-')
			{
				size = atoi(argv[i]);
				arg = NO_ARG;
			}
			else
			{
				print_help();
				return EXIT_FAILURE;
			}
		}
	}
	else if (argc < 2)
	{
		print_help();
		return EXIT_FAILURE;
	}
	
	FILE* fs;
	struct stat st;
	bool clear = false;

	if (stat(argv[1], &st) != 0)
	{
		st.st_mode = S_IFREG;
	}

	if (st.st_mode & S_IFREG)
	{
		printf("Found regular file.\n");
		fs = fopen(argv[1], "r+");
		if (fs == NULL && size == 0)
		{
			printf("Error: You must specify <total-size> for new or empty files.\n");
			fclose(fs);
			return EXIT_FAILURE;
		}
		else if (fs == NULL)
		{
			fs = fopen(argv[1], "w");
			if (fs == NULL)
			{
				printf("Error: Could not open file: %s\n", strerror(errno));
				return EXIT_FAILURE;
			}
			clear = true;
		}
		if (size == 0)
		{
			fseek(fs, 0, SEEK_END);
			size = ftello(fs);
			fseek(fs, 0, SEEK_SET);
		}
		else
		{
			fclose(fs);
			fs = fopen(argv[1], "w");
			clear = true;
		}
		if (nodesize == 0)
		{
			nodesize = 4096;
		}
	}
	else if (st.st_mode & S_IFBLK)
	{
		printf("Found block device.\n");
		if (st.st_blksize == 0 && nodesize == 0)
		{
			printf("Error: stat returned 0 block size\n");
			return EXIT_FAILURE;
		}
		if (nodesize == 0)
		{
			nodesize = st.st_blksize;
		}
		fs = fopen(argv[1], "r+");
		if (fs == NULL)
		{
			printf("Error: Could not open device: %s\n", strerror(errno));
			return EXIT_FAILURE;
		}
		fseek(fs, 0, SEEK_END);
		size = ftello(fs);
		fseek(fs, 0, SEEK_SET);
	}

	uint64_t nodes = floor((size - 6) / nodesize);
	if (nodes < 2)
	{
		printf("Error: Filesystem size is too small to hold two nodes.\n");
		fclose(fs);
		return EXIT_FAILURE;
	}

	if (nodesize < 9)
	{
		printf("Error: Node size is too small. Minimum is 9 (1 byte of data).\n");
	}
	if (nodesize < 1024)
	{
		printf("Notice: Node size is smaller than 1024 - this will work SLOWLY.\n");
	}

	if (clear == false)
	{
		clear = tools::format_stagfs(fs, nodesize, 1);
	}
	else
	{
		clear = tools::format_stagfs(fs, nodesize, nodes);
	}
		
	fclose(fs);
	if (!clear)
	{
		return EXIT_FAILURE;
	}	

	printf("Filesystem size: %llu\n", (nodes*nodesize)+6);
	printf("Node size: %u\n", nodesize);
	printf("Nodes: %llu\n", nodes);
	return EXIT_SUCCESS;
}

